前兩天讓 build 和部署都自動化了,但新版本部署上去之後,跑得好不好?今天用 Helm 安裝 kube-prometheus-stack 這套監控系統,先學會用 PromQL 查詢 Todo App 的狀況,明天再用 Grafana 把這些資料畫成 Dashboard。
服務跑在 K8s 上之後,要怎麼知道它的狀況?
kubectl top pods 只能看到當下的資源使用量,沒有歷史趨勢,也看不到應用程式層面的指標。
監控系統解決這個問題:持續收集指標、儲存歷史資料、在 Dashboard 上視覺化呈現。
Prometheus:指標收集和儲存的系統。它主動去各個服務的 /metrics 端點拉取指標(Pull-based),存成時序資料庫。
Grafana:視覺化工具。從 Prometheus 查詢資料,畫成圖表和 Dashboard。
K8s 元件 / 應用程式
└── 暴露 /metrics 端點
│
│ Prometheus 定期拉取
▼
Prometheus(儲存時序資料) ← 今天
│
│ Grafana 查詢
▼
Grafana Dashboard(視覺化) ← 明天
kube-prometheus-stack 安裝時已經設定好 Prometheus 去收集 K8s 元件的指標(Pod 的 CPU、記憶體、重啟次數等),不需要額外設定。
今天目標:用 Helm 安裝 kube-prometheus-stack,確認 Prometheus 收集了哪些資料,並用 PromQL 查詢 staging 的 CPU、記憶體和重啟次數。
# 1. 向本地 Helm 新增 Prometheus 社群官方維護的 Chart 儲存庫(別名取為 prometheus-community)
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
# 2. 更新本地儲存庫索引,確保拉取到最新的 Chart 版本資訊
helm repo update
# 3. 部署監控套件:
# - Release 名稱:monitoring
# - 安裝來源:prometheus-community/kube-prometheus-stack
# - 安裝 Namespace:monitoring(若不存在則由 --create-namespace 自動建立)
helm install monitoring prometheus-community/kube-prometheus-stack -n monitoring --create-namespace
Release 名稱用 monitoring,後面的 Service 名稱(monitoring-grafana、monitoring-kube-prometheus-prometheus 等)都以它開頭。
PowerShell 寫成一行即可。第一次安裝要拉不少 image,可能要等幾分鐘。
確認 Pod 都 Running:
kubectl get pods -n monitoring

一個 helm install 就裝好了 Prometheus、Grafana、Alertmanager 等六個元件和一系列 CRD。如果用 kubectl apply 手動管理,要處理十幾份 YAML 和複雜的設定,這就是 Helm 在大型系統上的價值。
現在叢集同時跑 dev、staging、ArgoCD 和監控,比較吃記憶體。Pod 卡在
Pending時,用kubectl describe pod看 Events,Insufficient memory/cpu代表資源不夠。
轉發到本機 9090,終端機保持開著:
kubectl port-forward svc/monitoring-kube-prometheus-prometheus -n monitoring 9090:9090
打開 http://localhost:9090,就是 Prometheus 內建的查詢介面。
上方選單 Status → Targets(新版介面叫 Target health),會列出 Prometheus 正在拉取的所有目標,每個目標都有一個 /metrics 端點:

狀態是 UP 代表拉取成功。之後如果查詢結果是空的,可以先來這裡確認目標有沒有正常運作。
今天用到的指標主要來自兩個地方:cAdvisor 負責「用了多少資源」,kube-state-metrics 負責「K8s 設定和狀態是什麼」。
在查詢框輸入:
kube_pod_info{namespace="staging"}

結果的每一行代表一個 Pod。右上角的 Result series: 6 表示共有 6 筆結果,也就是 staging 裡有 6 個 Pod:3 個 frontend、2 個 api、1 個 mysql。
每一行分成兩部分:
kube_pod_info,加上大括號裡一串 key="value"。這串叫做 label,用來描述這筆資料是誰,例如 pod="todo-staging-mysql-0" 是 Pod 名稱,node="minikube" 是它跑在哪台機器上。查詢時大括號裡寫的 namespace="staging",意思是「只留下 label 中 namespace 等於 staging 的資料」,這就是用 label 篩選。
你會發現每一行的值都是 1。這是因為 kube_pod_info 的用途是記錄 Pod 的基本資料,資訊都放在 label 裡,值本身沒有意義,固定是 1。
大部分指標的值是有意義的,但要怎麼看,取決於它屬於哪一種類型。最常見的有兩種,查詢寫法不一樣:
Gauge(量表):記錄「現在的狀態」
像體重計,數字會上下變動,看到的值本身就有意義。
例如 container_memory_working_set_bytes 的值是 524288000(約 500MB),就代表這個 container 現在用了 500MB 記憶體。這類指標直接查就好。
Counter(計數器):記錄「從開始到現在的累計總數」
像家裡的電錶,數字只會一直往上加,程式重啟時才歸零。
電錶顯示 12,345 度,你看不出家裡現在有沒有開冷氣,要看「過去一小時跑了幾度」才知道。Counter 也一樣,總數本身看不出現在的狀況,要看一段時間內增加了多少。
例如 kube_pod_container_status_restarts_total 查到 5,代表這個 container 總共重啟過 5 次。但這 5 次是上個月發生的,還是剛剛 10 分鐘內連續發生的?光看總數分不出來。
Counter 最常搭配這兩個函式,把累計值換算成變化量:
rate(指標[5m]):過去 5 分鐘內,平均每秒增加多少。適合看 CPU 這類「速度」。increase(指標[1h]):過去 1 小時內,總共增加多少。適合看重啟次數這類「次數」。看名字最快:
指標名稱以
_total結尾的通常是 counter,看到就要想到rate或increase。
不確定的話,切到 Graph 分頁看線的形狀:一路往上爬的是 Counter,上下起伏的是 Gauge。
接下來就實際用這些觀念,查詢 staging 的 CPU、記憶體和重啟次數。
直接看完整查詢會有點難懂,我們從原始指標開始,一層一層加上去。
開始之前,先確認 API 的 container 名稱,等一下會用它來篩選。另開一個終端機(port-forward 那個要保持開著)執行:
kubectl get deploy todo-staging-api -n staging -o jsonpath="{.spec.template.spec.containers[*].name}"
以下範例以 api 為例,名稱不同的話記得替換,否則查詢結果會是空的。
1. 原始指標
container_cpu_usage_seconds_total{namespace="staging"}
會出現很多條序列,除了 container="api",還有 container="" 這種沒有名稱的。那是 cAdvisor 額外提供的「整個 Pod 的總和」,如果不篩選掉,加總時會重複計算。
2. 篩選 container
container_cpu_usage_seconds_total{namespace="staging", container="api"}
這就只剩 API container 了。但它是 counter,最右邊的值是「啟動以來 CPU 總共工作了幾秒」,就像 Step4 說的電錶,只會一直變大,看不出現在忙不忙。
3. 用 rate 換算成每秒使用量
rate(container_cpu_usage_seconds_total{namespace="staging", container="api"}[5m])
加上 rate() 之後,值就從一直變大的累計秒數,變成「過去 5 分鐘平均每秒用了多少 CPU」。結果的單位是 core,0.1 代表 0.1 core,也就是 100m,可以直接和 Deployment 裡設定的 CPU requests 對照。[5m] 是計算的時間區間,區間越短越即時但越抖動,5 分鐘是常用的平衡點。
4. 依 Pod 分組
sum by (pod) (rate(container_cpu_usage_seconds_total{namespace="staging", container="api"}[5m]))
sum by (pod) 把同一個 Pod 的序列加總,結果每個 Pod 一條線,label 也只保留 pod,畫圖時乾淨很多。
點查詢框下方的 Graph 分頁,就能看到隨時間變化的曲線。

同樣的思路,再看三個常用查詢:
# API 每個 Pod 的記憶體使用量(來自 cAdvisor,gauge,不需要 rate)
sum by (pod) (container_memory_working_set_bytes{namespace="staging", container="api"})
# API 每個 Pod 的 memory limit(來自 kube-state-metrics)
sum by (pod) (kube_pod_container_resource_limits{namespace="staging", container="api", resource="memory"})
# staging 每個 Pod 過去 1 小時的重啟次數(counter,用 increase)
sum by (pod) (increase(kube_pod_container_status_restarts_total{namespace="staging"}[1h]))
記憶體用
working_set_bytes而不是usage_bytes,後者包含可回收的快取,數字會偏高。K8s 判斷是否 OOM 也是看 working set。
kube_pod_container_resource_limits 同時記錄了 CPU 和記憶體的 limit,所以要加上 resource="memory" 指定只取記憶體。
網路上有些範例會用 cAdvisor 的
container_spec_memory_limit_bytes查 limit,但在 kube-prometheus-stack 裡會查不到資料。因為它和 kube-state-metrics 的資料重複,kube-prometheus-stack 預設在收集時就把container_spec開頭的指標丟掉了。查不到資料時,除了檢查拼字和 label,也要想到「這個指標可能根本沒被收集」。
兩個查詢相除,就得到「記憶體使用率」:
sum by (pod) (container_memory_working_set_bytes{namespace="staging", container="api"})
/
sum by (pod) (kube_pod_container_resource_limits{namespace="staging", container="api", resource="memory"})
使用量來自 cAdvisor,limit 來自 kube-state-metrics,剛好對應 Step3 提到的兩個來源。
結果是 0 到 1 之間的數字,0.8 代表已經用了 limit 的 80%。這個值越接近 1,container 就越接近被 OOMKilled。
如果 container 沒有設定 memory limit,
kube_pod_container_resource_limits不會有這筆資料,相除結果會是空的。查不到東西時,先單獨查分母確認,再回頭檢查 Chart 的 resources 設定。
這些查詢是明天 Dashboard 和 Day 29 告警規則的基礎。
練習結束後,到 port-forward 的終端機按 Ctrl + C 停掉。
| 寫法 | 用途 |
|---|---|
指標{label="值"} |
用 label 篩選 |
指標{label=~"todo-.*"} |
用正規表示式篩選 |
rate(counter[5m]) |
counter 換算成每秒平均增加量 |
increase(counter[1h]) |
counter 在一段時間內的總增加量 |
sum by (pod) (...) |
依指定 label 分組加總 |
A / B |
兩組序列相除,label 相同的會配對 |
topk(3, ...) |
取數值最大的前 3 條 |
/metrics 端點拉取指標,存成時序資料rate 或 increase
namespace、container 篩選,sum by (pod) 依 Pod 分組working_set_bytes,limit 看 kube-state-metrics,相除就是離 OOM 還有多遠明天會用 Grafana 把今天的查詢畫成 Todo App 專屬的 Dashboard。